package com.jiwei.community.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jiwei.community.config.RedisConfig;
import com.jiwei.community.constants.SystemConstant;
import com.jiwei.community.dao.UserCommunityLevelMapper;
import com.jiwei.community.dao.UserPostScoreMapper;
import com.jiwei.community.entity.Post;
import com.jiwei.community.entity.RecommendScoreXY;
import com.jiwei.community.entity.User;
import com.jiwei.community.entity.UserPostScore;
import com.jiwei.community.service.PostService;
import com.jiwei.community.service.RecommendService;
import com.jiwei.community.service.UserPostScoreService;
import com.jiwei.community.service.UserService;
import com.jiwei.community.utils.BeanCopyUtils;
import com.jiwei.community.utils.RecommendUtils;
import com.jiwei.community.utils.RedisCache;
import com.jiwei.community.utils.SensitiveWordUtils;
import com.jiwei.community.vo.PageListVo;
import com.jiwei.community.vo.PostVo;
import com.jiwei.community.vo.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 季伟
 * @date 2024/4/3
 */
@Slf4j
@Service("recommendService")
public class RecommendServiceImpl implements RecommendService {

    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    UserCommunityLevelMapper userCommunityLevelMapper;
    @Resource
    UserPostScoreMapper userPostScoreMapper;
    @Resource
    PostService postService;
    @Autowired
    RedisCache redisCache;
    @Autowired
    UserService userService;
    /**
     * 更新redis中的每个用户的相似用户
     */
    public void cacheSimilarUser(){
        Set<String> set = redisTemplate.keys("recommend:similarity:*");
        redisTemplate.delete(set);
        List<RecommendScoreXY> xyList = userPostScoreMapper.getXYScoreList();//获取所有用户与其他用户的相同正反馈评分列表
        System.out.println(xyList);
        Map<Long,Set<ZSetOperations.TypedTuple<Long>>> map = new HashMap<>();//所有用户的与其他用户的相似度集合
        Map<Long,Map<Long,Double>> userWithSimilarUserSet  = new HashMap();
        for (RecommendScoreXY xy : xyList){
            List<Double> xScoreList = Arrays.stream(xy.getXScoreListStr().split(",")).map(Double::parseDouble).collect(Collectors.toList());
            List<Double> yScoreList = Arrays.stream(xy.getYScoreListStr().split(",")).map(Double::parseDouble).collect(Collectors.toList());
            Double similarity = 0D;
//            Integer sameCommunityNum = userCommunityLevelMapper.getSameCommunityNum(xy.getXUserId(),xy.getYUserId());
//            if (sameCommunityNum > 0){
//                similarity += SystemConstant.RECOMMEND_SIMILARITY_COMMUNITY_FACTOR*sameCommunityNum;
//            }
            try {
                Double scoreSimilarity = RecommendUtils.getScoreSimilarity(xScoreList,yScoreList);
                similarity += scoreSimilarity;
                similarity = Double.parseDouble(RecommendUtils.format.format(similarity));
                System.out.println("x("+xy.getXUserId()+")-y("+xy.getYUserId()+")-"+"similarity:"+similarity);
            }catch (Exception e){//推荐算法不适用的情况，例如当x和y中只有一个相同的正反馈帖子，导致计算皮尔森相似度时分母出现0
//                System.out.println(e.getMessage());
                e.printStackTrace();
            }

            if (similarity >0){
//                Set<ZSetOperations.TypedTuple<Long>> tuples=map.get(xy.getXUserId());
//                if (tuples==null){
//                    tuples = new HashSet<>();
//                    map.put(xy.getXUserId(),tuples);
//                }
//                tuples.add(new DefaultTypedTuple<>(xy.getYUserId(),similarity));
                Map<Long,Double> userYWithScore = userWithSimilarUserSet.get(xy.getXUserId());
                if (userYWithScore == null){
                    userYWithScore = new HashMap<>();
                    userWithSimilarUserSet.put(xy.getXUserId(),userYWithScore);
                }
                userYWithScore.put(xy.getYUserId(),similarity);
            }
        }
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

        List<User> userList = userService.list();
        userList.stream().peek(userX -> {
            userList.stream().peek(userY -> {
                if (!userX.getId().equals(userY.getId())){
                    Integer sameCommunityNum = userCommunityLevelMapper.getSameCommunityNum(userX.getId(),userY.getId());
                    Map<Long,Double> yAndScore = userWithSimilarUserSet.get(userX.getId());
                    if (yAndScore == null){
                        yAndScore = new HashMap<>();
                        userWithSimilarUserSet.put(userX.getId(),yAndScore);
                    }
                    Double similarity = yAndScore.get(userY.getId());
                    if (similarity == null){
                        yAndScore.put(userY.getId(),sameCommunityNum*SystemConstant.RECOMMEND_SIMILARITY_COMMUNITY_FACTOR);
                    }else {
                        yAndScore.put(userY.getId(),similarity+sameCommunityNum*SystemConstant.RECOMMEND_SIMILARITY_COMMUNITY_FACTOR);
                    }
                }
            }).collect(Collectors.toList());
        }).collect(Collectors.toList());

        userWithSimilarUserSet.keySet().stream().peek(xId->{
           Map<Long,Double> yAndScore = userWithSimilarUserSet.get(xId);
           yAndScore.keySet().stream().peek(yId->{
               Double similarity = yAndScore.get(yId);
               if (similarity!=null && !similarity.equals(0D)){
                   Set<ZSetOperations.TypedTuple<Long>> tuples=map.get(xId);
                   if (tuples==null){
                       tuples = new HashSet<>();
                       map.put(xId,tuples);
                   }
                   tuples.add(new DefaultTypedTuple<>(yId,similarity));
               }
           }).collect(Collectors.toList());
        }).collect(Collectors.toList());


        List list = map.entrySet().stream().peek(entry->{
            redisTemplate.opsForZSet().add("recommend:similarity:"+entry.getKey(),entry.getValue());
        }).collect(Collectors.toList());
    }
    public void cacheScoreList(){
        Set<String> set = redisTemplate.keys("recommend:similarity:*");
        redisTemplate.delete(set);
        List<UserPostScore> userPostScoreList = userPostScoreMapper.selectList(null);
        Map<Long,Set<ZSetOperations.TypedTuple<Long>>> map = new HashMap<>();
        for (UserPostScore score : userPostScoreList){
            Set<ZSetOperations.TypedTuple<Long>> tuples=map.get(score.getUserId());
            if (tuples==null){
                tuples = new HashSet<>();
                map.put(score.getUserId(),tuples);
            }
            tuples.add(new DefaultTypedTuple<>(score.getPostId(),score.getScore()));
        }
        map.entrySet().stream().peek(entry->{redisTemplate.opsForZSet().add("recommend:score:"+entry.getKey(),entry.getValue());}).collect(Collectors.toList());
    }

    /**
     * 获取推荐帖子列表
     * @param pageNum
     * @param pageSize
     * @param userId
     * @return
     */
    @Override
    public PageListVo<PostVo> getRecommendPosts(Integer pageNum, Integer pageSize, Long userId){
        Set<Long> likedPostId = new HashSet<>();
        List<Long> mayLikeId = new LinkedList<>();
        if (userId != null){
            likedPostId = redisTemplate.opsForZSet().range("recommend:score:"+userId,0,-1);
            Set<Long> similarityUserId = redisTemplate.opsForZSet().range("recommend:similarity:"+userId,0, SystemConstant.RECOMMEND_TOP_K);//获取前k个相似用户的id

            similarityUserId.stream().peek(id->{
                Set<Long> mayLikeIdOfOneUser = redisTemplate.opsForZSet().range("recommend:score:"+id,0,-1);
                mayLikeId.addAll(mayLikeIdOfOneUser);
            }).collect(Collectors.toList());
            System.out.println(mayLikeId);
        }


        log.info("user("+userId+")-likedPostId："+likedPostId.toString());
        log.info("user("+userId+")-mayLikedId："+mayLikeId);
        PageListVo<PostVo> pageListVo;
        if (mayLikeId.size() > 0){
            LambdaQueryWrapper<Post> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.in(mayLikeId.size()>0,Post::getId,mayLikeId);
            queryWrapper.notIn(likedPostId.size()>0,Post::getId, likedPostId);
            queryWrapper.eq(Post::getStatus,0);
            queryWrapper.orderByDesc(Post::getCreateAt);
            queryWrapper.orderByDesc(Post::getViewNum);
            Page<Post> page = new Page<>(pageNum,pageSize);
            postService.page(page,queryWrapper);
            List<PostVo> postVos = page.getRecords().stream().map(post -> {
                PostVo postVo = BeanCopyUtils.copyBean(post,PostVo.class);
                postVo.setTitle(SensitiveWordUtils.replaceWord(postVo.getTitle()));
                User user = userService.getById(post.getCreateBy());
                postVo.setPubAvatar(user.getAvatar());
                postVo.setPubUserName(user.getUserName());
                postVo.setPubNickName(user.getNickName());
                return postVo;
            }).collect(Collectors.toList());
            pageListVo = new PageListVo<>(page.getTotal(),postVos);
        }else{
            pageListVo = new PageListVo<>(0L,new LinkedList<>());
        }
        return pageListVo;
    }








//    @Override
//    public void updateScore(Long userId, Long postId, Integer increase, Integer type) {
//        switch (type){
//            case SystemConstant.RECOMMEND_SCORE_UPDATE_TYPE_VIEW:
//                updateScoreByView(userId,postId);
//                break;
//            case SystemConstant.RECOMMEND_SCORE_UPDATE_TYPE_COMMENT:
//                updateScoreByComment(userId,postId,increase);
//                break;
//            case SystemConstant.RECOMMEND_SCORE_UPDATE_TYPE_LIKE:
//                updateScoreByLike(userId,postId,increase);
//                break;
//            case SystemConstant.RECOMMEND_SCORE_UPDATE_TYPE_COLLECT:
//                updateScoreByCollect(userId,postId,increase);
//                break;
//        }
//    }



}
